import io
import sys, os
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from pytablericons import TablerIcons, OutlineIcon
from io import BytesIO
from mpl_toolkits.axes_grid1 import make_axes_locatable
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QSize
from PyQt5.QtGui import QColor, QBrush, QPixmap, QImage, QIcon
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAction, QFileDialog,
                             QTableWidget, QTableWidgetItem, QVBoxLayout,
                             QWidget, QToolBar, QStatusBar, QMessageBox,
                             QAbstractItemView, QSplitter,
                             QPushButton, QCheckBox, QGroupBox,
                             QLabel, QTableView, QGraphicsView, QGraphicsScene,
                             QMdiArea, QMdiSubWindow, QTextEdit, QSpinBox)

from matplotlib.backends.backend_qt5agg import (
    FigureCanvasQTAgg as FigureCanvas,
    NavigationToolbar2QT as NavigationToolbar)

from icon_handled import icon_handled


matplotlib.use("Agg")          # 非交互后端，避免弹独立窗口

class TableViewer(QMainWindow):
    def __init__(self):
        super().__init__()
        """界面布局"""
        self.setWindowTitle("表格阅读器")
        self.resize(2500, 1200)

        central = QWidget()  # 中央控件
        self.setCentralWidget(central)

        # 给 central 一个布局（随便用 QVBoxLayout / QHBoxLayout 都行）
        lay = QVBoxLayout(central)
        lay.setContentsMargins(0, 0, 0, 0)      # 可选：去掉边距

        # 创建一个最左侧（外层）的Qsplitter
        first_splitter = QSplitter(Qt.Horizontal, central)
        # 创建右侧的两个Qsplitter
        second_splitter = QSplitter(Qt.Horizontal)

        # 1、左边设置控件
        left_widget = QWidget()
        layout_left = QVBoxLayout()
        left_widget.setLayout(layout_left)

        self.grp0 = QGroupBox("数据预处理")
        left_box_layout_0 = QVBoxLayout(self.grp0)
        left_box_layout_0.addWidget(QLabel("数据保存位数"))
        self.decimal_digits = QSpinBox()
        self.decimal_digits.setValue(4)  # 默认值
        self.decimal_digits.setRange(0, 9)

        self.confirm_digits = QPushButton("确认")
        self.confirm_digits.clicked.connect(self.decimal_digits_reset)
        left_box_layout_0.addWidget(self.decimal_digits)
        left_box_layout_0.addWidget(self.confirm_digits)


        self.grp1 = QGroupBox("数据可视化")
        left_box_layout_1 = QVBoxLayout(self.grp1)
        self.visual_all = QPushButton("数据可视化（热图）")
        self.visual_all.clicked.connect(self.imagesc_data)

        self.check_table_color = QCheckBox("数据上色")
        self.check_table_color.setLayoutDirection(Qt.LeftToRight)
        self.check_table_color.stateChanged.connect(self.on_check_changed)

        left_box_layout_1.addWidget(self.check_table_color)
        left_box_layout_1.addWidget(self.visual_all)

        self.grp2 = QGroupBox("数据分析")
        left_box_layout_2 = QVBoxLayout(self.grp2)
        self.visual_line = QPushButton("数据比较（线图）")
        self.visual_local = QPushButton("数据比较（热图）")

        left_box_layout_2.addWidget(self.visual_line)
        left_box_layout_2.addWidget(self.visual_local)


        self.grp3 = QGroupBox("数据特征")
        left_box_layout_3 = QVBoxLayout(self.grp3)
        self.table_attrbute = QTextEdit("表格属性")
        left_box_layout_3.addWidget(self.table_attrbute)

        layout_left.addWidget(self.grp0)
        layout_left.addWidget(self.grp1)
        layout_left.addWidget(self.grp2)
        layout_left.addWidget(self.grp3)

        # layout.addWidget(QLabel("TextEdit"))
        # self.text_edit = QTextEdit("多行文字")
        # layout.addWidget(self.text_edit)

        # 2、中间设置表格
        self.table = QTableWidget()
        # 初始化选中数据的属性
        self.table_data = pd.DataFrame()
        self.selected_items = []
        self.selected_rows_data = []
        self.selected_column_data = []

        self.table.itemSelectionChanged.connect(self.on_selection_changed)

        self.visual_line.clicked.connect(self.plot_line_data)

        self.visual_local = QPushButton("数据比较（热图）")


        # 3、右侧设置画布
        self.mdi = QMdiArea()

        first_splitter.addWidget(left_widget)
        first_splitter.addWidget(second_splitter)
        second_splitter.addWidget(self.table)
        second_splitter.addWidget(self.mdi)

        # 设置区域的大小
        # second_splitter.setFixedWidth(400)

        # 6. 把最外层 splitter 塞进 central 的布局
        lay.addWidget(first_splitter)


        """"菜单栏 """
        menubar = self.menuBar()  # 菜单栏
        file_menu = menubar.addMenu("文件(&F)")
        help_menu = menubar.addMenu("帮助(&O)")

        # 导入icon的图片
        icon_open_file = TablerIcons.load(OutlineIcon.FOLDER_OPEN, size=48, color='blue')
        icon_rebuild_file = TablerIcons.load(OutlineIcon.FILE_PLUS, size=48, color='blue')
        icon_save_file = TablerIcons.load(OutlineIcon.DEVICE_FLOPPY, size=48, color='blue')
        icon_help_file = TablerIcons.load(OutlineIcon.HELP_CIRCLE, size=48, color='blue')

        pixmap_file_open = icon_handled(icon_open_file)
        pixmap_file_rebuild = icon_handled(icon_rebuild_file)
        pixmap_file_save = icon_handled(icon_save_file)
        pixmap_file_help = icon_handled(icon_help_file)

        # 打开
        open_action = QAction(QIcon(pixmap_file_open), "打开(&O)...", self)
        open_action.setShortcut("Ctrl+O")
        open_action.triggered.connect(self.open_file)
        file_menu.addAction(open_action)

        # 新建
        rebuild_action = QAction(QIcon(pixmap_file_rebuild), "新建(&O)...", self)
        rebuild_action.setShortcut("Ctrl+N")
        # open_action.triggered.connect(self.open_file)
        file_menu.addAction(rebuild_action)

        # 保存
        save_action = QAction(QIcon(pixmap_file_save), "保存(&S)...", self)
        save_action.setShortcut("Ctrl+S")
        save_action.triggered.connect(self.save_file)
        file_menu.addAction(save_action)

        # 帮助
        help_action = QAction(QIcon(pixmap_file_help), "帮助(&L)...", self)
        help_action.setShortcut("Ctrl+L")
        # help_action.triggered.connect(self.save_file)

        file_menu.addSeparator()
        file_menu.addAction(help_action)
        help_menu.addAction(help_action)

        """工具栏"""
        toolbar = QToolBar("主工具栏")  # 工具栏
        toolbar.setIconSize(QSize(32, 32))
        self.addToolBar(toolbar)

        toolbar.addAction(open_action)
        toolbar.addAction(rebuild_action)
        toolbar.addAction(save_action)

        """状态栏"""
        self.status = QStatusBar()
        self.setStatusBar(self.status)
        self.status.showMessage("就绪")



    def draw_matplotlib_to_scene(self, fig, ax):
        """新建一个可拖动、带 NavigationToolbar 的图像子窗口"""
        # 1. 创建 figure / canvas
        # fig, ax = self.plot_data()
        # fig, ax = self.imagesc_data()
        canvas = FigureCanvas(fig)

        # 2. 创建 toolbar
        toolbar = NavigationToolbar(canvas, self)

        # 3. 把 canvas + toolbar 放到一个 widget 里
        w = QWidget()
        lay = QVBoxLayout(w)
        lay.addWidget(toolbar)
        lay.addWidget(canvas)

        # 4. 子窗口
        sub = QMdiSubWindow()
        sub.setWidget(w)
        sub.setWindowTitle("Matplotlib 图")
        sub.resize(800, 600)
        # sub.setAttribute(Qt.WA_DeleteOnClose)

        # 5. 加入 mdi 区域
        self.mdi.addSubWindow(sub)
        sub.show()


    def imagesc_data(self):
        fig, ax = plt.subplots(figsize=(8, 6), dpi=200)
        data = self.get_table_data().values
        im = ax.imshow(data, cmap='turbo')

        # 手动给 colorbar 留 5 % 宽度，主图占 95 %
        divider = make_axes_locatable(ax)
        cax = divider.append_axes("right", size="10%", pad=0.1)
        fig.colorbar(im, cax=cax)

        self.draw_matplotlib_to_scene(fig, ax)


    def plot_data(self):
        # 1. 用 Matplotlib 生成一张图（随便画点什么）
        fig, ax = plt.subplots(figsize=(8, 6), dpi=200)
        x = np.linspace(0, 2 * np.pi, 100)
        ax.plot(x, np.sin(x), label='sin')
        ax.plot(x, np.cos(x), label='cos')
        ax.legend()
        ax.set_title("Matplotlib in Qt")

        return fig, ax

    def open_file(self):
        path, _ = QFileDialog.getOpenFileName(
            self, "打开表格", "", "Excel (*.xlsx);;CSV (*.csv);;All Files (*)")
        if not path:
            return
        try:
            if path.lower().endswith(".csv"):
                df = pd.read_csv(path)
            else:
                df = pd.read_excel(path)
                df.set_index(df.columns[0], inplace=True)
        except Exception as e:
            QMessageBox.critical(self, "读取失败", str(e))
            return

        self.populate_table(df)
        self.table_data = df  # 加载的表格数据

        # 简单分析表格的属性，输出在GUI界面的底部框内：
        self.analysing_data(df)

        self.status.showMessage(f"已加载：{os.path.basename(path)}")


    def analysing_data(self, df):
        row, col = df.shape
        column_names = df.columns.tolist()
        index_names = df.index.tolist()

        self.table_attrbute.setPlainText(f"1、表格大小为：{row}行{col}列；\n "
                                         f"2、表格的行标签为：\n"
                                         f"{index_names} \n"
                                         f"3、表格的列标签为：\n "
                                         f"{column_names}")

    def save_file(self):
        if self.table.rowCount() == 0:
            QMessageBox.information(self, "提示", "表格为空，无需保存。")
            return
        path, _ = QFileDialog.getSaveFileName(
            self, "保存表格", "", "Excel (*.xlsx);;CSV (*.csv);;All Files (*)")
        if not path:
            return
        rows, cols = self.table.rowCount(), self.table.columnCount()
        header = [self.table.horizontalHeaderItem(c).text() for c in range(cols)]
        data = [[self.table.item(r, c).text() if self.table.item(r, c) else ""
                 for c in range(cols)] for r in range(rows)]
        df = pd.DataFrame(data, columns=header)
        try:
            if path.lower().endswith(".csv"):
                df.to_csv(path, index=False, encoding='utf-8-sig')
            else:
                df.to_excel(path, index=False)
            self.status.showMessage(f"已保存：{os.path.basename(path)}")
        except Exception as e:
            QMessageBox.critical(self, "保存失败", str(e))

    def decimal_digits_reset(self):
        digits = self.decimal_digits.value()  # 获取修改好的小数位数
        df = self.table_data.copy()  # 获取表格数据
        # 截断小数位数
        df = np.round(df, digits) # 修改df，但是原始数据保持不变
        self.populate_table(df)
        self.check_table_color.setChecked(False)


    def populate_table(self, df):
        """
        把表格数据展示在GUI界面上
        :param df: pandas的数据结构，带有行Label和列Label
        :return: None
        """
        self.table.clear()
        self.table.setRowCount(len(df))
        self.table.setColumnCount(len(df.columns))
        self.table.setHorizontalHeaderLabels(df.columns.astype(str))
        self.table.setVerticalHeaderLabels(df.index.astype(str))
        for r in range(len(df)):
            for c in range(len(df.columns)):
                self.table.setItem(r, c, QTableWidgetItem(str(df.iat[r, c])))
        self.table.resizeColumnsToContents()  # 表格宽度自适应数据
        self.table.setAlternatingRowColors(True)  # 启用行颜色的交替显示
        self.table.setEditTriggers(QAbstractItemView.DoubleClicked)  # 双击改变数据


    def on_selection_changed(self):
        """
        获取选中的单元格数据和行号
        :return:
        """
        # 获取所有选中的数据
        selected_items = self.table.selectedItems()
        if selected_items:
            selected_data = [float(item.text()) for item in selected_items]

        # 获取选中的行列号
        selected_rows = self.table.selectionModel().selectedRows()
        selected_cols = self.table.selectionModel().selectedColumns()
        selected_rows_data = [row.row() for row in selected_rows]
        selected_column_data = [col.column() for col in selected_cols]

        # 把数据赋值给初始过self的特定属性
        self.selected_items = selected_data
        self.selected_rows_data = selected_rows_data
        self.selected_column_data = selected_column_data


    def plot_line_data(self):
        # 先获取数据
        selected_data = self.selected_items
        selected_rows_data = self.selected_rows_data
        selected_column_data = self.selected_column_data

        raw_names = np.array(self.table_data.columns)
        column_names = np.array(self.table_data.columns)

        if selected_rows_data:
            # 数据调整
            selected_data = np.array(selected_data)
            selected_data = selected_data.reshape(len(selected_rows_data), self.table_data.shape[1])

            # 作图
            fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(18, 10), layout='constrained')
            for i in range(len(selected_rows_data)):
                ax.plot(np.arange(1, len(column_names) + 1), selected_data[i, :],
                        linewidth=2, linestyle='-',
                        marker='o', markerfacecolor='white', markeredgecolor='black', markersize=8)
            ax.legend(raw_names[selected_rows_data])   # 添加legend

            self.draw_matplotlib_to_scene(fig, ax)




    def on_check_changed(self, state):
        if state == Qt.Checked:
            self.set_table_color()
        else:
            self.clear_table_color()

    def set_table_color(self):
        df = self.get_table_data()
        if df is None:
            return
        mn, mx = df.min().min(), df.max().max()
        if mx == mn:
            return
        cmap = plt.get_cmap('turbo')
        for r in range(self.table.rowCount()):
            for c in range(self.table.columnCount()):
                try:
                    val = float(self.table.item(r, c).text())
                    norm = (val - mn) / (mx - mn)
                    col = cmap(norm)
                    brush = QBrush(QColor(*(int(x * 255) for x in col[:3]), 128))
                    self.table.item(r, c).setBackground(brush)
                except ValueError:
                    pass
        self.table.update()  # 刷新表格显示

    def clear_table_color(self):
        for r in range(self.table.rowCount()):
            for c in range(self.table.columnCount()):
                self.table.item(r, c).setBackground(QBrush())
        self.table.update()  # 刷新表格显示

    def get_table_data(self):
        rows, cols = self.table.rowCount(), self.table.columnCount()
        header = [self.table.horizontalHeaderItem(c).text() for c in range(cols)]
        data = [[float(self.table.item(r, c).text()) if self.table.item(r, c) else ""
                 for c in range(cols)] for r in range(rows)]
        df = pd.DataFrame(data, columns=header)
        return df


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = TableViewer()
    win.show()
    sys.exit(app.exec_())




